package org.juiceshop.groovy.orientdb;

import groovy.lang.Closure;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.query.nativ.ONativeSynchQuery;
import com.orientechnologies.orient.core.query.nativ.OQueryContextNative;
import com.orientechnologies.orient.core.record.impl.ODocument;

class Table {
	private final Database _db;
	private final String _className;
	private final String _indexField;
	private OIndex<?> _index = null;

	public Table (Database db, String className, String indexField) {
		_db = db;
		_className = className;
		_indexField = indexField;
	}

	public ODocument create (Map<String,Object> values) {
		return _db.newInstance(_className).fields(values);
	}
	
	public ODocument save (Map<String,Object> values) {
		return create(values).save();
	}
	
	public List<ODocument> select (String clause, Object...args) {
		String statement = "select from "+_className+(clause!=null?" "+clause:"");
		return _db.querySQL(statement,args);		
	}
	
	public List<ODocument> findAll (Map<String,?> values) {
		StringBuilder builder = new StringBuilder("where ");
		Object[] args = new Object[values.size()];
		int count = 0;
		for (Map.Entry<String, ?> v : values.entrySet()) {
			String key = v.getKey();
			Object value = v.getValue();
			if (count>0) builder.append(" and ");
			builder.append(key);
			if (value instanceof String) {
				builder.append(" like ?");
			} else {
				builder.append("=?");
			}
			args[count++] = value;
		}
		//println(builder+' - '+args)
		return select(builder.toString(),args);
	}
	
	public List<ODocument> findAllX (final Map<String,?> values) {
		@SuppressWarnings({ "serial", "unchecked" })
		List<ODocument> result = _db.query(new ONativeSynchQuery<OQueryContextNative>
		     (_db, _className, new OQueryContextNative()) {
		  			@Override
		  			public boolean filter(OQueryContextNative iRecord) {
		  				for (Map.Entry<String,?> v : values.entrySet()) {
		  					String[] strings = v.getKey().split("\\.");
		  					for (String s : strings) {
		  						iRecord.field(s);
		  					}
		  					Object value = v.getValue();
		  					if (value instanceof String) {
		  						iRecord.like(value.toString());
		  					} else {
		  						iRecord.eq(value);
		  					}
		  				}
		  				return iRecord.go();
		  			};
		  		});
		  return result;
	}
			
	public ODocument find (Map<String,?> values) {
		List<ODocument> res = findAllX(values);
		return res.size()>0 ? res.get(0) : null;
	}
	
	
	public void browse (final Closure<Void> closure) {
		for (ODocument doc : _db.browseClass(_className)) closure.call(doc);
	}
	
	public void browse (final Map<String,?> values, final Closure<Void> closure) {
		for (ODocument doc : findAll(values)) {
			closure.call(doc);
		}		
	}
	
	public long count() {
		return _db.countClass(_className);
	}

	
	public List<ODocument> query (final Closure<OQueryContextNative> closure) {
		@SuppressWarnings({ "serial", "unchecked" })
		ONativeSynchQuery<OQueryContextNative> query = new ONativeSynchQuery<OQueryContextNative>(_db,_className,new OQueryContextNative()) {
			@Override
			public boolean filter(OQueryContextNative iRecord) {
				OQueryContextNative result = closure.call(iRecord);
				return result.go();
			}
		};
		return _db.query(query);
	}
	
	
	public OIndex<?> index(String indexField) {
		return _db.index(_className+'.'+indexField);
	}
	
	public OIndex<?> index() {
		if (_index==null) {
			if (_indexField!=null) {
				_index = index(_indexField);
			}
			if (_index==null) {
				throw new IllegalStateException("No default index defined for class "+_className);
			}
		}
		return _index;
	}

	public Object get(Object keyValue) {
		Object found = index().get(keyValue);
		if (found instanceof OIdentifiable) {
			return identToDoc((OIdentifiable)found);
		}
		if (found instanceof List<?>) {
			return identToDocList(toIdentifiableList(found));
		}
		throw new IllegalStateException("Found unknown result: "+found);
	}
	
	public ODocument getFirst(Object keyValue) {
		Object found = index().get(keyValue);
		OIdentifiable ident;
		if (found instanceof OIdentifiable) {
			ident = (OIdentifiable)found;
		} else if (found instanceof List<?>) {
			List<OIdentifiable> list = toIdentifiableList(found);
			ident = list.size()==0 ? null : list.get(0);
		} else {
			throw new IllegalStateException("Found unknown result: "+found);
		}
		return identToDoc(ident);
	}
	
	public List<ODocument> getAll(Object keyValue) {
		Object found = index().get(keyValue);
		List<OIdentifiable> identList;
		if (found instanceof OIdentifiable) {
			identList = Collections.singletonList((OIdentifiable)found);
		} else if (found instanceof List<?>) {
			identList = toIdentifiableList(found);
		} else {
			throw new IllegalStateException("Found unknown result: "+found);
		}
		return identToDocList(identList);
	}
	
	public Object getAt(String indexValue) {
		return getAt((Object)indexValue);
	}
	
	public Object getAt(Object indexValue) {
		return get(indexValue);
	}
	
	@SuppressWarnings("unchecked")
	private List<OIdentifiable> toIdentifiableList(Object obj) {
		return (List<OIdentifiable>)obj;
	}
	
	private ODocument identToDoc(OIdentifiable ident) {
		if (ident==null) return null;
		if (ident instanceof ODocument) return (ODocument)ident;
		if (ident instanceof ORID) return _db.load((ORID)ident);
		throw new IllegalArgumentException("Unknow result type "+ident);
	}
	
	private List<ODocument> identToDocList(List<OIdentifiable> identList) {
		List<ODocument> result = new LinkedList<>();
		for (OIdentifiable ident : identList) {
			result.add(identToDoc(ident));
		}
		return result;
	}
	
}

